package dkbta.ontology.elements.abstractions.state;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.Element;
import dkbta.ontology.elements.TypedName;
import dkbta.ontology.elements.context.Context;
import dkbta.ontology.loading.OntologyException;
import dkbta.processing.Patient;
import dkbta.util.InstanceTime;
import dkbta.util.time.TimeInterval;

public class AndMappingFunction extends StateMappingFunction{

	private final int _numOfAbstractedFromElements;

	/**
	 * Constructs an AND mapping function
	 * 
	 * @param numOfAbstractedFromElements
	 */
	public AndMappingFunction(Collection<Entry> entries, int numOfAbstractedFromElements){
		super(entries);
		_numOfAbstractedFromElements = numOfAbstractedFromElements;
	}

	/**
	 * Returns the type of the mapping function
	 * 
	 * @return The type of the mapping function
	 */
	@Override
	public StateMappingFunction.Type getType(){
		return StateMappingFunction.Type.AND;
	}

	@Override
	public void validate(Ontology ontology) throws OntologyException{
		super.validate(ontology);
		for (Entry entry : _entries){
			if (entry.getNumberOfConditionss() != _numOfAbstractedFromElements){
				throw new OntologyException(
						"AND mapping function requires that all entries "
								+ "contain constraints on all elements");
			}
		}
	}
	
	/**
	 * Goes over all of the instances of the abstracted-from elements obtained from the
	 * patient and attempts to abstract simultaneous occurrences of all abstracted-from
	 * elements and all necessary contexts
	 * 
	 * @param p The associated patient
	 * @param states The list to which to add the created states
	 * @param stateName The name of the state that will be created
	 * @param abstractedFrom The names and types of the elements the state is abstracted
	 *        from
	 * @param necessaryContexts The contexts necessary for the abstraction process
	 */
	@Override
	public void performMapping(Patient p, List<State> states, String stateName,
		TypedName[] abstractedFrom, String[] necessaryContexts){
		int numOfAF = abstractedFrom.length;
		int numOfNC = necessaryContexts.length;
		int numOfRequiredElements = numOfAF + numOfNC;

		List<InstanceTime> times = new ArrayList<InstanceTime>();
		for (int i = 0; i < numOfAF; ++i){
			// Obtaining for each abstracted-from element its instances
			TypedName af = abstractedFrom[i];
			List<? extends Element> elements = p.getElements(af.getElementType(), af
					.getName());

			// For each instance adding its start and end times to the times list
			for (Element e : elements){
				TimeInterval ti = e.getTimeInterval();
				times.add(new InstanceTime(e, i, ti.getStartTime(), true));
				times.add(new InstanceTime(e, i, ti.getEndTime(), false));
			}
		}
		for (int i = numOfAF; i < numOfRequiredElements; ++i){
			// Obtaining for each abstracted-from element its instances
			String contextName = necessaryContexts[i - numOfAF];
			List<Context> elements = p.getContexts(contextName);

			// For each instance adding its start and end times to the times list
			for (Element e : elements){
				TimeInterval ti = e.getTimeInterval();
				times.add(new InstanceTime(e, i, ti.getStartTime(), true));
				times.add(new InstanceTime(e, i, ti.getEndTime(), false));
			}
		}
		// Sorting the time list chronologically
		Collections.sort(times);

		// Going over the times to find all of the overlaps of ALL elements
		int currentlyOverlapping = 0;
		Element[] currentElements = new Element[numOfRequiredElements];
		for (InstanceTime it : times){
			if (it.isStart){
				++currentlyOverlapping;
				currentElements[it.elementIndex] = it.element;

				if (currentlyOverlapping == numOfRequiredElements){
					mapElements(states, stateName, currentElements, numOfAF);
				}
			}else{
				--currentlyOverlapping;
			}
		}
	}

	/**
	 * Creates an instance of this state from the given element instances and according to
	 * the function's nature
	 */
	private void mapElements(List<State> states, String stateName,
		Element[] instancesAndContexts, int numOfAF){
		// Computing the overlap between the intervals of all the instances and contexts
		TimeInterval overlap = new TimeInterval(0, Long.MAX_VALUE);
		for (Element instance : instancesAndContexts){
			overlap = overlap.getOverlap(instance.getTimeInterval());
			if (overlap == null){
				throw new IllegalArgumentException("Element instances don't overlap!");
			}
		}

		// Checking whether all of the instances adhere to their respective conditions
		for (Entry entry : _entries){
			// Checking whether all the elements match the conditions of the current
			// entry
			boolean conditionMatch = true;
			for (int i = 0; i < numOfAF; ++i){
				Element eInst = instancesAndContexts[i];
				if (!(conditionMatch = entry.checkCondition(eInst))){
					break;
				}
			}

			// If a match was found a state instance is created
			if (conditionMatch){
				states.add(new State(stateName, overlap, entry.getReturnValue()));
				return;
			}
		}
	}

}
